/*
 * UAE - The U*nix Amiga Emulator
 *
 * UAE Library v0.1
 *
 * (c) 1996 Tauno Taipaleenmaki <tataipal@raita.oulu.fi>
 *
 * Change UAE parameters and other stuff from inside the emulation.
 */

#include "sysconfig.h"
#include "gensound.h"
#include "dxwrap.h"
#include "picasso96_win.h"
#include "options.h"
#include "debug.h"
#include "uae.h"
#include "thread.h"
#include "newcpu.h"
#include "traps.h"
#include "disk.h"
#include "filesys.h"
#include "hardfile.h"

/*
 * Returns UAE Version
 */
static uint emulib_GetVersion()
{
    return version;
}

/*
 * Resets your amiga
 */
static uint emulib_HardReset()
{
    uae_reset(0);
    return 0;
}

static uint emulib_Reset()
{
    uae_reset(0);
    return 0;
}

/*
 * Enables SOUND
 */
static uint emulib_EnableSound(uint val)
{
    // if (!sound_available || g_curr_conf.sound_produce)
    //     return 0;

    g_curr_conf.sound_produce = (val > 1);
    return 1;
}

/*
 * Enables FAKE JOYSTICK
 */
static uint emulib_EnableJoystick(uint val)
{
    g_curr_conf.jports[0].id = val & 255;
    g_curr_conf.jports[1].id = (val >> 8) & 255;
    return 1;
}

/*
 * Sets the framerate
 */
static uint emulib_SetFrameRate(uint val)
{
    if (val == 0)
        return 0;
    else if (val > 20)
        return 0;
    else
    {
        g_curr_conf.gfx_framerate = val;
        return 1;
    }
}

/*
 * Changes keyboard language settings
 */
static uint emulib_ChangeLanguage(uint which)
{
    if (which > 6)
        return 0;
    else
    {
        switch (which)
        {
            case 0:
                g_curr_conf.keyboard_lang = KBD_LANG_US;
                break;
            case 1:
                g_curr_conf.keyboard_lang = KBD_LANG_DK;
                break;
            case 2:
                g_curr_conf.keyboard_lang = KBD_LANG_DE;
                break;
            case 3:
                g_curr_conf.keyboard_lang = KBD_LANG_SE;
                break;
            case 4:
                g_curr_conf.keyboard_lang = KBD_LANG_FR;
                break;
            case 5:
                g_curr_conf.keyboard_lang = KBD_LANG_IT;
                break;
            case 6:
                g_curr_conf.keyboard_lang = KBD_LANG_ES;
                break;
            default:
                break;
        }
        return 1;
    }
}

/* The following ones don't work as we never realloc the arrays... */
/*
 * Changes chip memory size
 *  (reboots)
 */
static uint REGPARAM2 emulib_ChgCMemSize(uint memsize)
{
    if (memsize != 0x80000 && memsize != 0x100000 &&
        memsize != 0x200000)
    {
        memsize = 0x200000;
        Logger::Write(L"Unsupported chipmem size!\n");
    }
    m68k_dreg(regs, 0) = 0;

    g_changed_conf.chipmem_size = memsize;
    uae_reset(0);
    return 1;
}

/*
 * Changes slow memory size
 *  (reboots)
 */
static uint REGPARAM2 emulib_ChgSMemSize(uint memsize)
{
    if (memsize != 0x80000 && memsize != 0x100000 &&
        memsize != 0x180000 && memsize != 0x1C0000)
    {
        memsize = 0;
        Logger::Write(L"Unsupported slowmem size!\n");
    }

    m68k_dreg(regs, 0) = 0;
    g_changed_conf.slowmem_size = memsize;
    uae_reset(0);
    return 1;
}

/*
 * Changes fast memory size
 *  (reboots)
 */
static uint REGPARAM2 emulib_ChgFMemSize(uint memsize)
{
    if (memsize != 0x100000 && memsize != 0x200000 &&
        memsize != 0x400000 && memsize != 0x800000)
    {
        memsize = 0;
        Logger::Write(L"Unsupported fastmem size!\n");
    }
    m68k_dreg(regs, 0) = 0;
    g_changed_conf.fastmem_size = memsize;
    uae_reset(0);
    return 0;
}

/*
 * Inserts a disk
 */
static uint emulib_InsertDisk(uae_ptr name, uint drive)
{
    int i = 0;
    char real_name[256];
    // TCHAR* s;

    if (drive > 3)
        return 0;

    while ((real_name[i] = get_byte(name + i)) != 0 && i++ != 254)
        ;

    if (i == 255)
        return 0;  /* ENAMETOOLONG */

    // s = Unicode::au(real_name);
    g_changed_conf.floppies[drive].df = real_name;
    // free(s);

    return 1;
}

/*
 * Exits the emulator
 */
static uint emulib_ExitEmu()
{
    uae_quit();
    return 1;
}

/*
 * Gets UAE Configuration
 */
static uint emulib_GetUaeConfig(uae_ptr place)
{
    int i, j;

    put_long(place, version);
    put_long(place + 4, allocated_chipmem);
    put_long(place + 8, allocated_slowmem);
    put_long(place + 12, allocated_fastmem);
    put_long(place + 16, g_curr_conf.gfx_framerate);
    put_long(place + 20, g_curr_conf.sound_produce ? 3 : 1);
    put_long(place + 24, g_curr_conf.jports[0].id | (g_curr_conf.jports[1].id << 8));
    put_long(place + 28, g_curr_conf.keyboard_lang);
    if (disk_empty(0))
        put_byte(place + 32, 0);
    else
        put_byte(place + 32, 1);
    if (disk_empty(1))
        put_byte(place + 33, 0);
    else
        put_byte(place + 33, 1);
    if (disk_empty(2))
        put_byte(place + 34, 0);
    else
        put_byte(place + 34, 1);
    if (disk_empty(3))
        put_byte(place + 35, 0);
    else
        put_byte(place + 35, 1);

    for (j = 0; j < 4; j++)
    {
        char* s = Unicode::ua(g_curr_conf.floppies[j].df);
        for (i = 0; i < 256; i++)
            put_byte(place + 36 + i + j * 256, s[i]);
        free(s);
    }
    return 1;
}

/*
 * Sets UAE Configuration
 *
 * NOT IMPLEMENTED YET
 */
static uint emulib_SetUaeConfig(uae_ptr place)
{
    return 1;
}

/*
 * Gets the name of the disk in the given drive
 */
static uint emulib_GetDisk(uint drive, uae_ptr name)
{
    // int i;
    if (drive > 3)
        return 0;

    for (int i = 0; i < 256; i++)
        put_byte(name + i, 0);

    for (int i = 0; i < g_curr_conf.floppies[drive].df.GetLength(); i++)
        put_byte(name + i, g_curr_conf.floppies[drive].df[i]);

    return 1;
}

/*
 * Enter debugging state
 */
static uint emulib_Debug()
{
    #ifdef DEBUGGER
    activate_debugger();
    return 1;
    #else
    return 0;
    #endif
}

#define CREATE_NATIVE_FUNC_PTR uint (* native_func)(uint, uint, uint, uint, uint, uint, uint, \
        uint, uint, uint, uint, uint, uint)
#define SET_NATIVE_FUNC(x) native_func = (uint (*)(uint, uint, uint, uint, uint, uint, uint, uint, uint, uint, uint, uint, uint))(x)
#define CALL_NATIVE_FUNC(d1, d2, d3, d4, d5, d6, d7, a1, a2, a3, a4, a5, a6) if (native_func) native_func(d1, d2, d3, d4, d5, d6, d7, a1, a2, a3, a4, a5, a6)
/* A0 - Contains a ptr to the native .obj data.  This ptr is Amiga-based. */
/*      We simply find the first function in this .obj data, and execute it. */
static uint REGPARAM2 emulib_ExecuteNativeCode()
{
    #if 0
    uae_ptr object_AAM = m68k_areg(regs, 0);
    uint d1 = m68k_dreg(regs, 1);
    uint d2 = m68k_dreg(regs, 2);
    uint d3 = m68k_dreg(regs, 3);
    uint d4 = m68k_dreg(regs, 4);
    uint d5 = m68k_dreg(regs, 5);
    uint d6 = m68k_dreg(regs, 6);
    uint d7 = m68k_dreg(regs, 7);
    uint a1 = m68k_areg(regs, 1);
    uint a2 = m68k_areg(regs, 2);
    uint a3 = m68k_areg(regs, 3);
    uint a4 = m68k_areg(regs, 4);
    uint a5 = m68k_areg(regs, 5);
    uint a6 = m68k_areg(regs, 6);

    byte* object_UAM = nullptr;
    CREATE_NATIVE_FUNC_PTR;

    if (get_mem_bank(object_AAM).check(object_AAM, 1))
        object_UAM = get_mem_bank(object_AAM).xlateaddr(object_AAM);

    if (object_UAM)
    {
        SET_NATIVE_FUNC(FindFunctionInObject(object_UAM));
        CALL_NATIVE_FUNC(d1, d2, d3, d4, d5, d6, d7, a1, a2, a3, a4, a5, a6);
    }
    return 1;
    #endif
    return 0;
}

static uint emulib_Minimize()
{
    return 0; // OSDEP_minimize_uae();
}

static int native_dos_op(uint mode, uint p1, uint p2, uint p3)
{
    TCHAR tmp[MAX_DPATH];
    char* s;
    int v, i;

    if (mode)
        return -1;
    /* receive native path from lock
     * p1 = dos.library:Lock, p2 = buffer, p3 = max buffer size
     */
    v = get_native_path(p1, tmp);
    if (v)
        return v;
    s = Unicode::ua(tmp);
    for (i = 0; i <= strlen(s) && i < p3 - 1; i++)
    {
        put_byte(p2 + i, s[i]);
        put_byte(p2 + i + 1, 0);
    }
    free(s);
    return 0;
}

extern uint picasso_demux(uint arg, TrapContext* context);

static uint REGPARAM2 uaelib_demux2(TrapContext* context)
{
#define ARG0 (get_long(m68k_areg(regs, 7) + 4))
#define ARG1 (get_long(m68k_areg(regs, 7) + 8))
#define ARG2 (get_long(m68k_areg(regs, 7) + 12))
#define ARG3 (get_long(m68k_areg(regs, 7) + 16))
#define ARG4 (get_long(m68k_areg(regs, 7) + 20))
#define ARG5 (get_long(m68k_areg(regs, 7) + 24))

    if (ARG0 >= 16 && ARG0 <= 39)
        return picasso_demux(ARG0, context);

    switch (ARG0)
    {
        case 0: return emulib_GetVersion();
        case 1: return emulib_GetUaeConfig(ARG1);
        case 2: return emulib_SetUaeConfig(ARG1);
        case 3: return emulib_HardReset();
        case 4: return emulib_Reset();
        case 5: return emulib_InsertDisk(ARG1, ARG2);
        case 6: return emulib_EnableSound(ARG1);
        case 7: return emulib_EnableJoystick(ARG1);
        case 8: return emulib_SetFrameRate(ARG1);
        case 9: return emulib_ChgCMemSize(ARG1);
        case 10: return emulib_ChgSMemSize(ARG1);
        case 11: return emulib_ChgFMemSize(ARG1);
        case 12: return emulib_ChangeLanguage(ARG1);
        /* The next call brings bad luck */
        case 13: return emulib_ExitEmu();
        case 14: return emulib_GetDisk(ARG1, ARG2);
        case 15: return emulib_Debug();

        case 68: return emulib_Minimize();
        case 69: return emulib_ExecuteNativeCode();

        case 70: return 0; /* RESERVED. Something uses this.. */

        case 80: return 0xffffffff; // g_curr_conf.maprom ? g_curr_conf.maprom : 0xffffffff;
        case 81: return 0; // cfgfile_uaelib(ARG1, ARG2, ARG3, ARG4);
        case 82: return cfgfile_uaelib_modify(ARG1, ARG2, ARG3, ARG4, ARG5);
        case 83: return 0; // g_curr_conf.mmkeyboard = ARG1 ? 1 : 0; return g_curr_conf.mmkeyboard;
            #ifdef DEBUGGER
        case 84: return mmu_init(ARG1, ARG2, ARG3);
            #endif
        case 85: return native_dos_op(ARG1, ARG2, ARG3, ARG4);
        case 86:
            if (valid_address(ARG1, 1))
            {
                TCHAR* s = Unicode::au((char*)get_real_address(ARG1));
                Logger::Write(L"DBG: %s\n", s);
                free(s);
                return 1;
            }
            return 0;
        case 87:
        {
            uint d0, d1;
            d0 = emulib_target_getcpurate(ARG1, &d1);
            m68k_dreg(regs, 1) = d1;
            return d0;
        }
    }
    return 0;
}

extern int uaelib_debug;
static uint REGPARAM2 uaelib_demux(TrapContext* context)
{
    uint v;
    struct regstruct* r = &regs;

    if (Logging::UAELIB_DEBUG)
        Logger::Write(L"%d: %08x %08x %08x %08x %08x %08x %08x %08x, %08x %08x %08x %08x %08x %08x %08x %08x\n",
            ARG0,
            r->regs[0], r->regs[1], r->regs[2], r->regs[3], r->regs[4], r->regs[5], r->regs[6], r->regs[7],
            r->regs[8], r->regs[9], r->regs[10], r->regs[11], r->regs[12], r->regs[13], r->regs[14], r->regs[15]);
    v = uaelib_demux2(context);
    if (Logging::UAELIB_DEBUG)
        Logger::Write(L"=%08x\n", v);
    return v;
}

/*
 * Installs the UAE LIBRARY
 */
void emulib_install()
{
    uae_ptr a;
    if (!uae_boot_rom)
        return;
    a = here();
    // g_curr_conf.mmkeyboard = 0;
    org(rtarea_base + 0xFF60);
    // #if 0
    // dw(0x4eb9);
    // dw((rtarea_base >> 16) | get_word(rtarea_base + 36));
    // dw(get_word(rtarea_base + 38) + 12);
    // #endif
    calltrap(deftrapres(uaelib_demux, 0, L"uaelib_demux"));
    dw(RTS);
    org(a);
}